﻿using System;
using System.Collections.Generic;
using System.Linq;

using System.Web.Mvc;
using Lilium.Web.Mvc;
using Lilium.Web.Mvc.Metadata;
using Lilium.Web.Mvc.State;
using System.Linq.Expressions;
using Lilium.Control;

namespace Lilium.Web.Mvc.EntityFilters
{
    public abstract class EntityFilter<TEntity, TState> : ControllerService, IEntityFilter<TEntity>, IStatefulObject
        where TState : ICloneable, IEquatable<TState>, new()
    {
        public EntityFilter(ControllerBase controller)
            : base(controller)
        {
        }

        public string GetStateParameterName(Expression<Func<TState, object>> getStateParamFunc)
        {
            if (getStateParamFunc == null) throw new ArgumentNullException("getStateParamFunc");

            var body = getStateParamFunc.Body;

            var propertyName =
                (from member in body.Cast<MemberExpression>()
                 select member.Member.Name)
                .Or
                (from unary in body.Cast<UnaryExpression>()
                 where unary.NodeType == ExpressionType.Convert
                 from member in unary.Operand.Cast<MemberExpression>()
                 select member.Member.Name).Run();

            var stateService = Controller.GetService<IStateService>(true);

            return stateService.GetStateParameterName(this, propertyName);
        }

        #region IEntityFilter<TEntity> Members

        public abstract IQueryable<TEntity> Apply(IQueryable<TEntity> entities);

        #endregion

        #region IEntityFilter Members

        public Type EntityType
        {
            get
            {
                return typeof(TEntity);
            }
        }

        #endregion

        #region IStatefulObject Members

        public virtual void SetState(object state)
        {
            if (state == null) throw new ArgumentNullException("state");

            State = (TState)((ICloneable)state).Clone();
        }

        /// <summary>
        /// Returns copy of state of the filter.
        /// </summary>
        /// <param name="actionName">Action name to create state for</param>
        /// <returns>State object or null if object is in default state.</returns>
        public object GetState(string actionName)
        {
            var state = OnGetState(actionName);
            if (!DefaultState.Equals(state))
                return state;
            else
                return null;
        }

        #region public string Key

        private string m_Key;

        public string Key
        {
            get
            {
                if (m_Key == null)
                {
                    string filterName;
                    var thisType = GetType();
                    if (thisType.IsGenericType)
                    {
                        var genericTypeName = GetType().GetGenericTypeDefinition().Name;
                        filterName = genericTypeName.Substring(0, genericTypeName.Length - 2) + "." + Controller.GetService<IMetadataService>(true).GetShortModelName(EntityType);
                    }
                    else
                    {
                        filterName = thisType.Name;
                    }
                    m_Key = filterName;
                }
                return m_Key;
            }
        }

        #endregion

        #region public virtual Type SateType

        public virtual Type StateType
        {
            get
            {
                return typeof(TState);
            }
        }

        #endregion

        #endregion

        /// <remarks>
        /// Default implementation ignores action parameter and always returns copy of the current object's state.
        /// To support filter actions specific implementation must override this method and ajust state according 
        /// to the <paramref name="actionName"/> parameter.
        /// </remarks>
        protected virtual TState OnGetState(string actionName)
        {
            return (TState)State.Clone();
        }

        #region private TState State

        private TState m_State = new TState();

        protected TState State
        {
            get { return m_State; }
            private set { m_State = value; }
        }

        #endregion

        #region private TState DefaultState

        private static readonly TState m_DefaultState = new TState();

        private static TState DefaultState
        {
            get
            {
                return m_DefaultState;
            }
        }

        #endregion

    }
}